package away3d.core.base;


import away3d.animators.data.AnimationBase ;
import away3d.events.GeometryEvent ;
import flash.display3D.Context3D ;
import flash.display3D.IndexBuffer3D ;
import flash.display3D.VertexBuffer3D ;



/**
 * The SubGeometry class is a collections of geometric data that describes a triangle mesh. It is owned by a
 * Geometry instance, and wrapped by a SubMesh in the scene graph.
 * Several SubGeometries are grouped so they can be rendered with different materials, but still represent a single
 * object.
 *
 * @see away3d.core.base.Geometry
 * @see away3d.core.base.SubMesh
 */
class SubGeometry
{
    
    private var _parentGeometry             : Geometry ;

    // raw data:
    private  var _vertices                  : Vector<Float> ;
    private  var _uvs                       : Vector<Float> ;
    private  var _secondaryUvs              : Vector<Float> ;
    private  var _vertexNormals             : Vector<Float> ;
    private  var _vertexTangents            : Vector<Float> ;
    private  var _indices                   : Vector<Int> ;
    private  var _faceNormalsData           : Vector<Float> ;
    private  var _faceWeights               : Vector<Float> ;
    private  var _faceTangents              : Vector<Float> ;

    // buffers:
    private  var _vertexBuffer              : Vector<VertexBuffer3D> ;
    private  var _uvBuffer                  : Vector<VertexBuffer3D> ;
    private  var _secondaryUvBuffer         : Vector<VertexBuffer3D> ;
    private  var _vertexNormalBuffer        : Vector<VertexBuffer3D> ;
    private  var _vertexTangentBuffer       : Vector<VertexBuffer3D> ;
    private  var _indexBuffer               : Vector<IndexBuffer3D> ;

    private var _autoDeriveVertexNormals    : Bool ;
    private var _autoDeriveVertexTangents   : Bool ;
    private var _useFaceWeights             : Bool ;
    private  var _maxIndex                  : Int ;

    // raw data dirty flags:
    private  var _faceNormalsDirty          : Bool ;
    private  var _faceTangentsDirty         : Bool ;
    private  var _vertexNormalsDirty        : Bool ;
    private  var _vertexTangentsDirty       : Bool ;

    // buffer dirty flags, per context:
    private  var _vertexBufferDirty         : Vector<Bool> ;
    private  var _uvBufferDirty             : Vector<Bool> ;
    private  var _secondaryUvBufferDirty    : Vector<Bool> ;
    private  var _indexBufferDirty          : Vector<Bool> ;
    private  var _vertexNormalBufferDirty   : Vector<Bool> ;
    private  var _vertexTangentBufferDirty  : Vector<Bool> ;

    private  var _numVertices               : Int  ;
    private  var _numIndices                : Int  ;
    private  var _numTriangles              : Int  ;

    /**
     * Creates a new SubGeometry object.
     */
    public function new()
    {
        
        _vertexBuffer               = new Vector( 8 ) ;
        _uvBuffer                   = new Vector( 8 ) ;
        _secondaryUvBuffer          = new Vector( 8 ) ;
        _vertexNormalBuffer         = new Vector( 8 ) ;
        _vertexTangentBuffer        = new Vector( 8 ) ;
        _indexBuffer                = new Vector( 8 ) ;
        _autoDeriveVertexNormals    = true ;
        _autoDeriveVertexTangents   = true ;
        _useFaceWeights             = false ;
        _maxIndex                   = -1 ;
        _faceNormalsDirty           = true ;
        _faceTangentsDirty          = true ;
        _vertexNormalsDirty         = true ;
        _vertexTangentsDirty        = true ;
        _vertexBufferDirty          = new Vector( 8 ) ;
        _uvBufferDirty              = new Vector( 8 ) ;
        _secondaryUvBufferDirty     = new Vector( 8 ) ;
        _indexBufferDirty           = new Vector( 8 ) ;
        _vertexNormalBufferDirty    = new Vector( 8 ) ;
        _vertexTangentBufferDirty   = new Vector( 8 ) ;
        
    }
    
    
    public var animation( get_animation, null ) : AnimationBase ;
    
    
    /**
     * The animation that affects the geometry.
     */
    private function get_animation() : AnimationBase
    {
        
        return _parentGeometry._animation ;
        
    }
    
    
    public var numVertices( get_numVertices, null ) : numVertices ;
    
    
    /**
     * The total amount of vertices in the SubGeometry.
     */
    private function get_numVertices() : uint
    {
        
        return _numVertices ;
        
    }
    
    
    public var numTriangles( get_numTrianges, null ) : Int ;
    
    
    /**
     * The total amount of triangles in the SubGeometry.
     */
    private function get_numTriangles() : Int
    {
        
        return _numTriangles ;
        
    }
    
    
    public var autoDeriveVertexNormals( get_autoDeriveVertexNormals, set_autoDeriveVertexNormals ) : Bool ;
    
    
    /**
     * True if the vertex normals should be derived from the geometry, false if the vertex normals are set
     * explicitly.
     */
    private function get_autoDeriveVertexNormals() : Bool
    {
        
        return _autoDeriveVertexNormals ;
        
    }
    
    
    private function set_autoDeriveVertexNormals( value : Bool ) : Bool
    {
        
        _autoDeriveVertexNormals = value ;
        
        if( !value )
        {
            
            _vertexNormalsDirty = false ;
            
        }
        
        return value ;
        
    }
    
    
    public var useFaceWeights( get_useFaceWeights, set_useFaceWeights ) : Bool ;
    
    
    /**
     * Indicates whether or not to take the size of faces into account when auto-deriving vertex normals and tangents.
     */
    private function get_useFaceWeights() : Bool
    {
        
        return _useFaceWeights ;
        
    }
    
    
    private function set_useFaceWeights( value : Bool ) : Bool
    {
        
        _useFaceWeights = value ;
        if( _autoDeriveVertexNormals ) _vertexNormalsDirty = true ;
        if( _autoDeriveVertexTangents ) _vertexTangentsDirty = true ;
        _faceNormalsDirty = true ;
        
        return value ;
        
    }
    
    
    public var autoDeriveVertexTangents( get_autoDeriveVertexTangents, set_autoDeriveVertexTangents ) : Bool ;
    
    
    /**
     * True if the vertex tangents should be derived from the geometry, false if the vertex normals are set
     * explicitly.
     */
    private function get_autoDeriveVertexTangents() : Bool
    {
        
        return _autoDeriveVertexTangents ;
        
    }
    
    
    private function set_autoDeriveVertexTangents( value : Bool ) : Bool
    {
        
        _autoDeriveVertexTangents = value ;
        
        if( !value )
        {
            
            _vertexTangentsDirty = false ;
        
        }
        
        return value ;
        
    }
    
    
    /**
     * Retrieves the VertexBuffer3D object that contains vertex positions.
     * @param context The Context3D for which we request the buffer
     * @return The VertexBuffer3D object that contains vertex positions.
     */
    public function getVertexBuffer( context : Context3D, contextIndex : Int ) : VertexBuffer3D
    {
        
        if( contextIndex > _maxIndex ) _maxIndex = contextIndex ;
        
        if( _vertexBufferDirty[ contextIndex ] || !_vertexBuffer[ contextIndex ] ) 
        {
            
            if( _vertexBuffer[ contextIndex ] == null )
            {
                
                _vertexBuffer[ contextIndex ] = context.createVertexBuffer( _numVertices, 3 ) ;
                
            }
            
            VertexBuffer3D( _vertexBuffer[ contextIndex ] ).uploadFromVector( _vertices, 0, _numVertices ) ;
            
            _vertexBufferDirty[ contextIndex ] = false ;
            
        }
        
        return _vertexBuffer[ contextIndex ] ;
        
    }
    
    
    /**
     * Retrieves the VertexBuffer3D object that contains texture coordinates.
     * @param context The Context3D for which we request the buffer
     * @return The VertexBuffer3D object that contains texture coordinates.
     */
    public function getUVBuffer( context : Context3D, contextIndex : Int ) : VertexBuffer3D
    {
        if( contextIndex > _maxIndex ) _maxIndex = contextIndex ;

        if( _uvBufferDirty[ contextIndex ] || !_uvBuffer[ contextIndex ] )
        {
            
            if( _uvBuffer[ contextIndex ] == null )
            {
                
                _uvBuffer[ contextIndex ] = context.createVertexBuffer( _numVertices, 2 ) ;
                
            }
            
            _uvBuffer[ contextIndex ].uploadFromVector( _uvs, 0, _numVertices ) ;
            
            _uvBufferDirty[ contextIndex ] = false ;
            
        }
        
        return _uvBuffer[ contextIndex ] ;
        
    }
    
    
    public function getSecondaryUVBuffer( context : Context3D, contextIndex : Int ) : VertexBuffer3D
    {
        
        if( contextIndex > _maxIndex ) _maxIndex = contextIndex ;
        
        if( _secondaryUvBufferDirty[ contextIndex ] || !_secondaryUvBuffer[ contextIndex ] )
        {
            
            if( _secondaryUvBuffer[ contextIndex ] == null )
            {
                
                _secondaryUvBuffer[ contextIndex ] = context.createVertexBuffer( _numVertices, 2 ) ;
                
            }
            
            _secondaryUvBuffer[ contextIndex ].uploadFromVector( _secondaryUvs, 0, _numVertices ) ;
            
            _secondaryUvBufferDirty[ contextIndex ] = false ;
            
        }
        
        return _secondaryUvBuffer[ contextIndex ] ;
        
    }
    
    
    /**
     * Retrieves the VertexBuffer3D object that contains vertex normals.
     * @param context The Context3D for which we request the buffer
     * @return The VertexBuffer3D object that contains vertex normals.
     */
    public function getVertexNormalBuffer( context : Context3D, contextIndex : Int ) : VertexBuffer3D
    {
        
        if( contextIndex > _maxIndex ) _maxIndex = contextIndex ;
        
        if( _autoDeriveVertexNormals && _vertexNormalsDirty ) updateVertexNormals() ;

        if( _vertexNormalBufferDirty[ contextIndex ] || !_vertexNormalBuffer[contextIndex] )
        {
            if( _vertexNormalBuffer[contextIndex] == null )
            {
                
                _vertexNormalBuffer[contextIndex] ||= context.createVertexBuffer( _numVertices, 3 ) ;
                
            }
            (  ).uploadFromVector( _vertexNormals, 0, _numVertices ) ;
            _vertexNormalBufferDirty[contextIndex] = false ;
        }

        return _vertexNormalBuffer[contextIndex] ;
    }

    /**
     * Retrieves the VertexBuffer3D object that contains vertex tangents.
     * @param context The Context3D for which we request the buffer
     * @return The VertexBuffer3D object that contains vertex tangents.
     */
    public function get_VertexTangentBuffer( context : Context3D, contextIndex : Int ) : VertexBuffer3D
    {
        if( contextIndex > _maxIndex ) _maxIndex = contextIndex ;

        if( _vertexTangentsDirty )
            updateVertexTangents() ;

        if( _vertexTangentBufferDirty[ contextIndex ] || !_vertexTangentBuffer[ contextIndex ] )
        {
            
            if( _vertexTangentBuffer[ contextIndex ]  == null )
            {
                
                _vertexTangentBuffer[ contextIndex ] = context.createVertexBuffer( _numVertices, 3 )
                
            }
            
            _vertexTangentBuffer[ contextIndex ].uploadFromVector( _vertexTangents, 0, _numVertices ) ;
            
            _vertexTangentBufferDirty[ contextIndex ] = false ;
            
        }
        
        return _vertexTangentBuffer[ contextIndex ] ;
        
    }
    
    
    /**
     * Retrieves the VertexBuffer3D object that contains triangle indices.
     * @param context The Context3D for which we request the buffer
     * @return The VertexBuffer3D object that contains triangle indices.
     */
    public function getIndexBuffer( context : Context3D, contextIndex : Int ) : IndexBuffer3D
    {
        
        if( contextIndex > _maxIndex ) _maxIndex = contextIndex ;
        
        if( _indexBufferDirty[contextIndex] || !_indexBuffer[contextIndex] ) 
        {
            
            if( _indexBuffer[contextIndex] == null )
            {
                
                _indexBuffer[ contextIndex ] = context.createIndexBuffer( _numIndices ) ;
                
            }
            
            _indexBuffer[ contextIndex ].uploadFromVector( _indices, 0, _numIndices ) ;
            
            _indexBufferDirty[ contextIndex ] = false ;
            
        }
        
        return _indexBuffer[ contextIndex ] ;
        
    }
    
    
    /**
     * Clones the current object
     * @return An exact duplicate of the current object.
     */
    public function clone() : SubGeometry
    {
        var clone = new SubGeometry() ;
        clone.updateVertexData( _vertices.concat() ) ;
        clone.updateUVData( _uvs.concat() ) ;
        clone.updateSecondaryUVData( _secondaryUvs.concat() ) ;
        clone.updateIndexData( _indices.concat() ) ;
        if( !_autoDeriveVertexNormals )  clone.updateVertexNormalData( _vertexNormals.concat() ) ;
        if( !_autoDeriveVertexTangents ) clone.updateVertexTangentData( _vertexTangents.concat() ) ;
        
        return clone ;
        
    }
    
    
    /**
     * Scales the geometry.
     * @param scale The amount by which to scale.
     */
    public function scale( scale : Float )
    {
        var len = _vertices.length ;
        for ( i in 0 to len )
        {
            
            _vertices[ i ] *= scale ;
            
        }
        
        invalidateBuffers( _vertexBufferDirty ) ;
        
    }
    
    
    /**
     * Scales the uv coordinates.
     * @param scale The amount by which to scale.
     */
    public function scaleUV( scale : Float )
    {
        var len = _uvs.length ;
        for ( i in 0...len )
        {
            
            _uvs[ i ] *= scale ;
            
        }
        
        invalidateBuffers( _uvBufferDirty ) ;
        
    }
    
    
    /**
     * Clears all resources used by the SubGeometry object.
     */
    public function dispose()
    {
        
        disposeVertexBuffers( _vertexBuffer ) ;
        disposeVertexBuffers( _vertexNormalBuffer ) ;
        disposeVertexBuffers( _uvBuffer ) ;
        disposeVertexBuffers( _secondaryUvBuffer ) ;
        disposeVertexBuffers( _vertexTangentBuffer ) ;
        disposeIndexBuffers( _indexBuffer ) ;
        
    }
    
    
    var vertexData( get_vertextData, null ): Vector<Float> ;
    
    
    /**
     * The raw vertex position data.
     */
    private function get_vertexData() : Vector<Float>
    {
        
        return _vertices ;
        
    }

    /**
     * Updates the vertex data of the SubGeometry.
     * @param vertices The new vertex data to upload.
     */
    public function updateVertexData( vertices : Vector<Float> )
    {
        
        if( _autoDeriveVertexNormals ) _vertexNormalsDirty   = true ;
        if( _autoDeriveVertexTangents ) _vertexTangentsDirty = true ;
        
        _faceNormalsDirty   = true ;
        _vertices           = vertices ;
        _numVertices        = vertices.length / 3 ;
        
        invalidateBuffers( _vertexBufferDirty ) ;
        
        invalidateBounds() ;
        
    }
    
    
    private function invalidateBounds()
    {
        
        if( _parentGeometry ) _parentGeometry.invalidateBounds( this ) ;
        
    }
    
    
    public var UVData( get_UVData, null ) : Vector<Float> ;
    
    
    /**
     * The raw texture coordinate data.
     */
    private function get_UVData() : Vector<Float>
    {
        
        return _uvs ;
        
    }
    
    
    public var secondaryUVData( get_secondaryUVData, set_secondaryUVData ) : Vector<Float> ;
    
    
    private function get_secondaryUVData() : Vector<Float>
    {
        
        return _secondaryUvs ;
        
    }
    
    
    /**
     * Updates the uv coordinates of the SubGeometry.
     * @param uvs The uv coordinates to upload.
     */
    public function updateUVData( uvs : Vector<Float> )
    {
        
        // normals don't get dirty from this
        if( _autoDeriveVertexTangents ) _vertexTangentsDirty = true ;
        _faceTangentsDirty  = true ;
        _uvs                = uvs ;
        invalidateBuffers( _uvBufferDirty ) ;
        
    }
    
    
    public function updateSecondaryUVData( uvs : Vector<Float> )
    {
        
        _secondaryUvs = uvs ;
        invalidateBuffers( _secondaryUvBufferDirty ) ;
        
    }
    
    
    public var vertexNormalData( get_vertexNormalData, null ) : Vector<Float> ;
    
    
    /**
     * The raw vertex normal data.
     */
    private function get_vertexNormalData() : Vector<Float>
    {
        
        if( _autoDeriveVertexNormals && _vertexNormalsDirty ) updateVertexNormals() ;
        return _vertexNormals ;
        
    }
    
    
    /**
     * Updates the vertex normals of the SubGeometry. When updating the vertex normals like this,
     * autoDeriveVertexNormals will be set to false and vertex normals will no longer be calculated automatically.
     * @param vertexNormals The vertex normals to upload.
     */
    public function updateVertexNormalData( vertexNormals : Vector<Float> )
    {
        
        _vertexNormalsDirty         = false ;
        _autoDeriveVertexNormals    = ( vertexNormals == null ) ;
        _vertexNormals              = vertexNormals ;
        
        invalidateBuffers( _vertexNormalBufferDirty ) ;
        
    }
    
    
    public var vertexTangentData( get_vertexTangentData, null ) : Vector<Float> ;
    
    
    /**
     * The raw vertex tangent data.
     *
     * @private
     */
    private function get_vertexTangentData() : Vector<Float>
    {
        
        if( _autoDeriveVertexTangents && _vertexTangentsDirty ) updateVertexTangents() ;
        
        return _vertexTangents ;
        
    }
    
    
    /**
     * Updates the vertex tangents of the SubGeometry. When updating the vertex tangents like this,
     * autoDeriveVertexTangents will be set to false and vertex tangents will no longer be calculated automatically.
     * @param vertexTangents The vertex tangents to upload.
     */
    public function updateVertexTangentData( vertexTangents : Vector<Float> )
    {
        
        _vertexTangentsDirty        = false ;
        _autoDeriveVertexTangents   = ( vertexTangents == null ) ;
        _vertexTangents             = vertexTangents ;
        
        invalidateBuffers( _vertexTangentBufferDirty ) ;
        
    }
    
    
    public var indexData( get_indexData, set_indexData ): Vector<Int> ;
    
    
    /**
     * The raw index data that define the faces.
     *
     * @private
     */
    private function get_indexData() : Vector<Int>
    {
        
        return _indices ;
        
    }
    
    
    /**
     * Updates the face indices of the SubGeometry.
     * @param indices The face indices to upload.
     */
    public function updateIndexData( indices : Vector<Int> )
    {
        
        _indices            = indices ;
        _numIndices         = indices.length ;
        _numTriangles       = _numIndices/3 ;
        
        invalidateBuffers( _indexBufferDirty ) ;
        
        _faceNormalsDirty   = true ;
        
        if( _autoDeriveVertexNormals )  _vertexNormalsDirty = true ;
        if( _autoDeriveVertexTangents ) _vertexTangentsDirty = true ;
        
    }
    
    
    public var faceNormalsData( get_faceNormalsData, null ) : Vector<Float> ;
    
    
    /**
     * The raw data of the face normals, in the same order as the faces are listed in the index list.
     *
     * @private
     */
    private function get_faceNormalsData() : Vector<Float>
    {
        
        if( _faceNormalsDirty ) updateFaceNormals() ;
        return _faceNormalsData ;
        
    }
    
    
    public var parentGeometry( get_parentGeometry, set_parentGeometry ) : Geometry ;
    
    
    /**
     * The Geometry object that 'owns' this SubGeometry object.
     *
     * @private
     */
    private function get_parentGeometry() : Geometry
    {
        
        return _parentGeometry ;
        
    }
    
    
    private function set_parentGeometry( value: Geometry )
    {
        
        _parentGeometry = value ;
        return value ;
        
    }
    
    
    /**
     * Invalidates all buffers in a vector, causing them the update when they are first requested.
     * @param buffers The vector of buffers to invalidate.
     */
    public function invalidateBuffers( buffers: Vector<Bool> )
    {
        //TODO: check this condition
        for( i in 0...( _maxIndex + 1 ) )
        {
            
            buffers[ i ] = true ;
            
        }
        
    }
    
    
    /**
     * Disposes all buffers in a given vector.
     * @param buffers The vector of buffers to dispose.
     */
    public function disposeVertexBuffers( buffers: Vector<VertexBuffer3D> )
    {
        //TODO: check this condition
        for( i in 0...( _maxIndex + 1 ) ) 
        {
            
            if( buffers[ i ] ) buffers[ i ].dispose() ;
            
        }
        
    }
    
    //TODO: this seems same as method above??
    /**
     * Disposes all buffers in a given vector.
     * @param buffers The vector of buffers to dispose.
     */
    private  function disposeIndexBuffers( buffers : Vector<IndexBuffer3D> )
    {
        //TODO: check this condition
        for( i in 0...( _maxIndex + 1 ) ) 
        {
            
            if( buffers[ i ] ) buffers[ i ].dispose() ;
            
        }
    }
    
    /**
     * Updates the vertex normals based on the geometry.
     */
    private function updateVertexNormals()
    {
        
        if( _faceNormalsDirty ) updateFaceNormals() ;
        
        var v1      : Int ;
        var v2      : Int ;
        var v3      : Int ;
        var f1      = 0 ;
        var f2      = 1 ;
        var f3      = 2 ;
        var lenV    = _vertices.length ;
        
        // reset, yo
        if( _vertexNormals )
        {
            
            while( v1 < lenV ) 
            {
                
                _vertexNormals[ v1++ ] = 0.0 ;
                
            }
            
        }
        else 
        {
            
            _vertexNormals = new Vector( _vertices.length, true ) ;
            
        }
        
        var i       : Int ;
        var k       : Int ;
        var lenI    = _indices.length ;
        var index   : Int ;
        var weight  : Int ;

        while ( i < lenI )
        {
            
            weight                      = _useFaceWeights? _faceWeights[ k++ ] : 1 ;
            index                       = _indices[ i++ ]*3 ;
            _vertexNormals[ index++ ]   += _faceNormalsData[ f1 ]*weight ;
            _vertexNormals[ index++ ]   += _faceNormalsData[ f2 ]*weight ;
            _vertexNormals[ index   ]   += _faceNormalsData[ f3 ]*weight ;
            index                       = _indices[ i++ ]*3 ;
            _vertexNormals[ index++ ]   += _faceNormalsData[ f1 ]*weight ;
            _vertexNormals[ index++ ]   += _faceNormalsData[ f2 ]*weight ;
            _vertexNormals[ index   ]   += _faceNormalsData[ f3 ]*weight ;
            index                       = _indices[ i++ ]*3 ;
            _vertexNormals[ index++ ]   += _faceNormalsData[ f1 ]*weight ;
            _vertexNormals[ index++ ]   += _faceNormalsData[ f2 ]*weight ;
            _vertexNormals[ index   ]   += _faceNormalsData[ f3 ]*weight ;
            f1                          += 3 ;
            f2                          += 3 ;
            f3                          += 3 ;
            
        }

        v1 = 0 ; 
        v2 = 1 ; 
        v3 = 2 ;
        
        while( v1 < lenV )
        {
            
            var vx                  = _vertexNormals[ v1 ] ;
            var vy                  = _vertexNormals[ v2 ] ;
            var vz                  = _vertexNormals[ v3 ] ;
            var d                   = 1.0/Math.sqrt( vx*vx + vy*vy + vz*vz ) ;
            _vertexNormals[ v1 ]    *= d ;
            _vertexNormals[ v2 ]    *= d ;
            _vertexNormals[ v3 ]    *= d ;
            v1                      += 3 ;
            v2                      += 3 ;
            v3                      += 3 ;
            
        }

        _vertexNormalsDirty = false ;
        invalidateBuffers( _vertexNormalBufferDirty ) ;
        
    }
    
    
    /**
     * Updates the vertex tangents based on the geometry.
     */
    private function updateVertexTangents()
    {
        
        if( _vertexNormalsDirty ) updateVertexNormals() ;
        if( _faceTangentsDirty )  updateFaceTangents() ;
        
        var v1      : Int ;
        var v2      : Int ;
        var v3      : Int ;
        var f1      = 0 ;
        var f2      = 1 ;
        var f3      = 2 ;
        var lenV    = _vertices.length ;

        if( _vertexTangents )
        {
            
            while( v1 < lenV )
            {
                
                _vertexTangents[ v1++ ] = 0.0 ;
                
            }
            
        }
        else 
        {
            
            _vertexTangents = new Vector( _vertices.length, true ) ;
            
        }
        
        var i       : Int ;
        var k       : Int  ;
        var lenI    = _indices.length ;
        var index   : Int  ;
        var weight  : Int  ;
        
        while ( i < lenI )
        {
            
            weight                      = _useFaceWeights? _faceWeights[ k++ ] : 1 ;
            index                       = _indices[ i++ ]*3 ;
            _vertexTangents[ index++ ]  += _faceTangents[ f1 ]*weight ;
            _vertexTangents[ index++ ]  += _faceTangents[ f2 ]*weight ;
            _vertexTangents[ index   ]  += _faceTangents[ f3 ]*weight ;
            index                       = _indices[ i++ ]*3 ;
            _vertexTangents[ index++ ]  += _faceTangents[ f1 ]*weight ;
            _vertexTangents[ index++ ]  += _faceTangents[ f2 ]*weight ;
            _vertexTangents[ index   ]  += _faceTangents[ f3 ]*weight ;
            index                       = _indices[ i++ ]*3 ;
            _vertexTangents[ index++ ]  += _faceTangents[ f1 ]*weight ;
            _vertexTangents[ index++ ]  += _faceTangents[ f2 ]*weight ;
            _vertexTangents[ index   ]  += _faceTangents[ f3 ]*weight ;
            f1                          += 3 ;
            f2                          += 3 ;
            f3                          += 3 ;
            
        }

        v1 = 0 ; 
        v2 = 1 ; 
        v3 = 2 ;
        
        while( v1 < lenV )
        {
            
            var vx              = _vertexTangents[ v1 ] ;
            var vy              = _vertexTangents[ v2 ] ;
            var vz              = _vertexTangents[ v3 ] ;
            var d               = 1.0/Math.sqrt( vx*vx + vy*vy + vz*vz ) ;
            _vertexTangents[v1] *= d ;
            _vertexTangents[v2] *= d ;
            _vertexTangents[v3] *= d ;
            v1                  += 3 ;
            v2                  += 3 ;
            v3                  += 3 ;
            
        }
        
        _vertexTangentsDirty = false ;
        invalidateBuffers( _vertexTangentBufferDirty ) ;
        
    }
    
    
    /**
     * Updates the normals for each face.
     */
    private function updateFaceNormals()
    {
        
        var i       : Int ;
        var j       : Int ;
        var k       : Int ;
        var index   : Int  ;
        var len     = _indices.length ;
        var x1      : Float ;
        var x2      : Float ; 
        var x3      : Float ;
        var y1      : Float ;
        var y2      : Float ;
        var y3      : Float ;
        var z1      : Float ;
        var z2      : Float ; 
        var z3      : Float ;
        var dx1     : Float ;
        var dy1     : Float ;
        var dz1     : Float ;
        var dx2     : Float ;
        var dy2     : Float ; 
        var dz2     : Float ;
        var cx      : Float ;
        var cy      : Float ;
        var cz      : Float ;
        var d       : Float ;
        
        if( _faceNormalsData == null )
        {
            
            _faceNormalsData = new Vector( len, true ) ;
            
        }
        
        if( _useFaceWeights )
        {
            if( _faceWeights == null )
            {
                
                _faceWeights = new Vector( len/3, true ) ;
                
            }
            
        }
        
        while ( i < len ) 
        {
            
            index   = _indices [ i++     ]*3 ;
            x1      = _vertices[ index++ ] ;
            y1      = _vertices[ index++ ] ;
            z1      = _vertices[ index   ] ;
            index   = _indices [ i++     ]*3 ;
            x2      = _vertices[ index++ ] ;
            y2      = _vertices[ index++ ] ;
            z2      = _vertices[ index   ] ;
            index   = _indices [ i++     ]*3 ;
            x3      = _vertices[ index++ ] ;
            y3      = _vertices[ index++ ] ;
            z3      = _vertices[ index   ] ;
            
            dx1     = x3 - x1 ;
            dy1     = y3 - y1 ;
            dz1     = z3 - z1 ;
            dx2     = x2 - x1 ;
            dy2     = y2 - y1 ;
            dz2     = z2 - z1 ;
            
            cx      = dz1*dy2 - dy1*dz2 ;
            cy      = dx1*dz2 - dz1*dx2 ;
            cz      = dy1*dx2 - dx1*dy2 ;
            
            d   = Math.sqrt( cx*cx+cy*cy+cz*cz ) ;
            
            // length of cross product = 2*triangle area
            if( _useFaceWeights ) 
            {
                
                var w               = d*10000 ;
                if( w < 1 )       w = 1 ;
                _faceWeights[ k++ ] = w ;
                
            }
            
            d                       = 1/d ;
            _faceNormalsData[ j++ ] = cx*d ;
            _faceNormalsData[ j++ ] = cy*d ;
            _faceNormalsData[ j++ ] = cz*d ;
            
        }
        
        _faceNormalsDirty           = false ;
        _faceTangentsDirty          = true ;
        
    }
    
    
    /**
     * Updates the tangents for each face.
     */
    private function updateFaceTangents()
    {
        
        var i       : Int ;
        var j       : Int  ;
        var index1  : Int ;
        var index2  : Int ;
        var index3  : Int ;
        var len     = _indices.length ;
        var ui      : Int ; 
        var vi      : Int ;
        var v0      : Float ;
        var dv1     : Float ;
        var dv2     : Float ;
        var denom   : Float ;
        var x0      : Float ;
        var y0      : Float ;
        var z0      : Float ;
        var dx1     : Float ;
        var dy1     : Float ;
        var dz1     : Float ;
        var dx2     : Float ;
        var dy2     : Float ;
        var dz2     : Float ;
        var cx      : Float ;
        var cy      : Float ; 
        var cz      : Float ;
        
        if( _faceTangents == null )
        {
            
            _faceTangents = new Vector<Float>( _indices.length, true ) ;
            
        }
        
        while( i < len )
        {
            
            index1                  = _indices[ i++ ] ;
            index2                  = _indices[ i++ ] ;
            index3                  = _indices[ i++ ] ;
            
            v0                      = _uvs[ ( index1 << 1 ) + 1 ] ;
            ui                      = index2 << 1 ;
            dv1                     = _uvs[ ( index2 << 1 ) + 1 ] - v0 ;
            ui                      = index3 << 1 ;
            dv2                     = _uvs[ ( index3 << 1 ) + 1 ] - v0 ;
            
            vi                      = index1*3 ;
            x0                      = _vertices[ vi     ] ;
            y0                      = _vertices[ vi + 1 ] ;
            z0                      = _vertices[ vi + 2 ] ;
            vi                      = index2*3 ;
            dx1                     = _vertices[ vi     ] - x0 ;
            dy1                     = _vertices[ vi + 1 ] - y0 ;
            dz1                     = _vertices[ vi + 2 ] - z0 ;
            vi                      = index3*3 ;
            dx2                     = _vertices[ vi     ] - x0 ;
            dy2                     = _vertices[ vi + 1 ] - y0 ;
            dz2                     = _vertices[ vi + 2 ] - z0 ;
            
            cx                      = dv2*dx1 - dv1*dx2 ;
            cy                      = dv2*dy1 - dv1*dy2 ;
            cz                      = dv2*dz1 - dv1*dz2 ;
            denom                   = 1/Math.sqrt( cx*cx + cy*cy + cz*cz ) ;
            _faceTangents[ j++ ]    = denom*cx ;
            _faceTangents[ j++ ]    = denom*cy ;
            _faceTangents[ j++ ]    = denom*cz ;
            
        }
        
        _faceTangentsDirty = false ;
        
    }
    
    
}